package com.sarc.games;

import java.util.concurrent.locks.ReentrantLock;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import org.opencv.calib3d.Calib3d;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;

public abstract class GameSkeleton /*extends Thread*/ implements Renderer {
	protected static final String TAG = "SARC.GAMESKELETON";
	SensorMathematics sensor;
	//Ball[][] balls = new Ball[4][4];
	private ReentrantLock camPosLock, camImageLock;
	
	private boolean shouldDrawScene = false;
	private boolean shouldBlurBackground = false;
	private Size blurringKernelSize = new Size(3,3);
	protected final float screenWidth, screenHeight;
	
	protected Mat rv=null, tv=null; 
	protected Mat uv=null, vv=null;
	private float[] bgColor;
	private Mat image = null;
	private GLSurfaceView view = null;
	
	public GameSkeleton(GLSurfaceView view, SensorMathematics sensor, float screenWidth, float screenHeight)
	{
		this.view = view;
		this.sensor = sensor;
        camPosLock = new ReentrantLock();
        camImageLock = new ReentrantLock();
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;
        bgColor = new float[]{1f,1f,1f,1f};
 		NativeInit();
	}
	
	protected void setBackgroundEffects(int newKernelSize, float[] bgColor)
	{
		
		this.shouldBlurBackground = newKernelSize > 1;
		this.blurringKernelSize = new Size(newKernelSize, newKernelSize);
		this.bgColor = bgColor;
	}
	
	protected float[] getBackgroundColor()
	{
		return this.bgColor;
	}
	
	protected int getBackgroundBlurLevel()
	{
		return (int)this.blurringKernelSize.height;
	}
	
	public void setNormal(Mat uv, Mat vv)
	{
		camPosLock.lock();
		try
		{
			if (uv == null)
			{
				this.uv = new Mat(uv.size(), uv.type());
			}
			
			if (vv == null)
			{
				this.vv = new Mat(vv.size(), vv.type());
			}
			
			uv.copyTo(this.uv);
			vv.copyTo(this.vv);
		}
		finally
		{
			camPosLock.unlock();
		}
	}
	
	public void setNewCameraPosition(Mat rv, Mat tv)
	{
		camPosLock.lock();
		try
		{
			if (this.rv == null)
			{
				this.rv = new Mat(rv.size(), rv.type());
			}
			
			if (this.tv == null)
			{
				this.tv = new Mat(tv.size(), tv.type());
			}
			
			rv.copyTo(this.rv);
			tv.copyTo(this.tv);
			
			NativeUpdateCameraPosition(this.rv.nativeObj, this.tv.nativeObj);
			Log.d(TAG, "Camera position updated" + tv.dump() + rv.dump());
		}
		finally
		{
			camPosLock.unlock();
		}
	}
	
	public void setShouldDrawScene(boolean shouldDrawScene)
	{
		this.shouldDrawScene = shouldDrawScene;
	}
	
	
	public void setNewImageBuffer(Mat img)
	{
		camImageLock.lock();
		try
		{
			if (this.image == null)
			{
				this.image = new Mat(img.size(), img.type());
				Log.d(TAG, "created new image");
			}
			
			if (shouldBlurBackground)
			{
				Imgproc.blur(img, image, blurringKernelSize);
			}
			else
			{
				img.copyTo(image);
			}
		}
		finally
		{
			camImageLock.unlock();
		}
		//this.image = img;
	}
	
	public void setReferencePoint(Mat refPt)
	{
		float[] data = new float[1];
		
		refPt.get(0, 0, data);
		float refX = data[0];
		
		refPt.get(0, 1, data);
		float refY = data[0];
		
		refPt.get(0, 2, data);
		float refZ = data[0];

		NativeUpdateReferencePoint(refX, refY, refZ);
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		
		if (image != null)
		{
			NativePreRender();
	        renderBackground(gl);
	        
	        if (shouldDrawScene)
	        {   	
	        	NativePreRenderPreScene();
	        	drawGamePreScene(gl);
	        	NativePreRenderScene();
	        	drawGameScene(gl);
	        	NativePostRenderScene();
	        }
	        NativePostRender();
		}
		
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		NativeRendererResize(width, height);
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig arg1) {
		NativeRendererSetUp();
	}
	
	private void renderBackground(GL10 gl)
	{
		//NativeSetUp();
		camImageLock.lock();
		try
		{
			gl.glColor4f(bgColor[0], bgColor[1], bgColor[2], bgColor[3]);
			NativeRenderBackground(image.nativeObj);
			gl.glColor4f(1f, 1f, 1f, 1f);
		}
		finally
		{
			camImageLock.unlock();
		}
	}
	
	@Override
	protected void finalize()
	{
		NativeRelease();
	}
	
	protected GLSurfaceView getView()
	{
		return view;
	}
	
	//Methods to be implemented
	abstract protected void onShouldDrawSceneChanged(boolean shouldDrawScene);
	abstract protected void drawGamePreScene(GL10 gl);
	abstract protected void drawGameScene(GL10 gl);
	
	//Setting up environment for rendering
	private native boolean NativeInit();
	private native boolean NativeRelease();
	private native boolean NativeRendererResize(int width, int height);
	private native boolean NativeRendererSetUp();
	private native boolean NativeRendererCleanUp();
	
	//Control functions
	private native boolean NativeUpdateReferencePoint(float x, float y, float z);
	private native boolean NativeUpdateCameraPosition(long addrMatRv, long addrMatTv);
	
	//Rendering codes
	private native boolean NativePreRender();
	private native boolean NativeRenderBackground(long addrBgMat);
	
	private native boolean NativePreRenderPreScene();
	
	private native boolean NativePreRenderScene();
	protected native boolean NativeRenderTriangle(long addrTv, long addrRv);
	private native boolean NativePostRenderScene();
	
	private native boolean NativePostRender();
}
